package org.ytor.core.aspect;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.ytor.common.anno.AutoLog;
import org.ytor.common.util.Jsons;
import org.ytor.core.CommonApi;
import org.ytor.core.holder.U;
import org.ytor.core.model.LoginUser;
import org.ytor.core.sysapi.log.model.SysLog;
import org.ytor.core.util.ReqUtil;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * created by yangtong on 2025/4/12 09:15:31
 * <br/>
 * 日志切面
 */
@Aspect
@Component
public class LogAspect {

    private final CommonApi commonApi;
    private final ObjectMapper mapper;

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    public LogAspect(CommonApi commonApi, ObjectMapper globalMapper) {
        this.commonApi = commonApi;
        this.mapper = globalMapper.copy().setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }

    @Around(value = "@annotation(autoLog)", argNames = "joinPoint,autoLog")
    public Object around(ProceedingJoinPoint joinPoint, AutoLog autoLog) throws Throwable {
        //方法定位
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        String classMethod = className + "." + methodName;

        //方法参数
        String[] paramNames = signature.getParameterNames();
        Object[] paramValues = joinPoint.getArgs();
        Map<String, Object> paramMap = new HashMap<>(paramNames.length);
        for (int i = 0; i < paramNames.length; i++) {
            String paramName = paramNames[i];
            Object paramValue = paramValues[i];
            if (paramValue != null) {
                paramMap.put(paramName, paramValue);
            }
        }
        String params = mapper.writeValueAsString(paramMap);

        Object result = null;
        Throwable error = null;

        long startTime = System.currentTimeMillis();
        try {
            result = joinPoint.proceed();
            return result;
        } catch (Throwable ex) {
            error = ex;
            //一定要重新抛出，让业务感知到异常
            throw ex;
        } finally {
            long duration = System.currentTimeMillis() - startTime;

            //记录日志
            int resultLength = Jsons.toJsonStr(result).length();
            if (result != null && resultLength > 1024) {
                result = result.toString().substring(0, 1024);
            }
            log.info("方法名称：【{}】，入参：【{}】，返回结果：【{}】，耗时：【{}】", classMethod, params, result, duration);

            Throwable finalError = error;
            HttpServletRequest request = ReqUtil.getRequest();
            LoginUser loginUser = U.get();
            commonApi.addAsyncLog(() -> {
                SysLog sysLog = new SysLog()
                        .setType(1)
                        .setThread(Thread.currentThread().toString())
                        .setClassName(className)
                        .setMethodName(methodName)
                        .setParams(params)
                        .setContent(autoLog.value())
                        .setCost(duration);
                if (request != null) {
                    sysLog.setIp(ReqUtil.getClientIp(request))
                            .setRequestUrl(request.getRequestURI())
                            .setResultLength(resultLength);
                }
                if (loginUser != null) {
                    sysLog.setCreateBy(loginUser.getUserName());
                    sysLog.setUpdateBy(loginUser.getUserName());
                }
                if (finalError != null) {
                    sysLog.setErrorMsg(finalError.getMessage());
                    sysLog.setErrorStack(Arrays.toString(finalError.getStackTrace()));
                }

                sysLog.setRemark(autoLog.remark());
                return sysLog;
            });
        }
    }

}
